#!/usr/bin/env python

"""Ninja toolchain abstraction for XCode toolchain"""

import os
import subprocess

import toolchain
import syntax


def make_target(toolchain, host, target):
    return XCode(toolchain, host, target)


class XCode(object):
    def __init__(self, toolchain, host, target):
        self.toolchain = toolchain
        self.host = host
        self.target = target

    def initialize_toolchain(self):
        self.organisation = ""
        self.bundleidentifier = ""
        self.provisioning = ""
        if self.target.is_macos():
            self.deploymenttarget = "12.0"
        elif self.target.is_ios():
            self.deploymenttarget = "15.0"

    def build_toolchain(self):
        if self.target.is_macos():
            sdk = "macosx"
            deploytarget = "MACOSX_DEPLOYMENT_TARGET=" + self.deploymenttarget
        elif self.target.is_ios():
            sdk = "iphoneos"
            deploytarget = "IPHONEOS_DEPLOYMENT_TARGET=" + self.deploymenttarget

        platformpath = toolchain.check_last_output(
            ["xcrun", "--sdk", sdk, "--show-sdk-platform-path"]
        )
        localpath = (
            platformpath
            + "/Developer/usr/bin:/Applications/Xcode.app/Contents/Developer/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin"
        )

        self.plist = (
            "PATH="
            + localpath
            + " "
            + toolchain.check_last_output(["xcrun", "--sdk", sdk, "-f", "plutil"])
        )
        self.xcassets = (
            "PATH="
            + localpath
            + " "
            + toolchain.check_last_output(["xcrun", "--sdk", sdk, "-f", "actool"])
        )
        self.xib = (
            "PATH="
            + localpath
            + " "
            + toolchain.check_last_output(["xcrun", "--sdk", sdk, "-f", "ibtool"])
        )
        self.dsymutil = (
            "PATH="
            + localpath
            + " "
            + toolchain.check_last_output(["xcrun", "--sdk", sdk, "-f", "dsymutil"])
        )

        self.plistcmd = "build/ninja/plist.py --exename $exename --prodname $prodname --bundle $bundleidentifier --target $target --deploymenttarget $deploymenttarget --output $outpath $in"
        if self.target.is_macos():
            self.xcassetscmd = (
                "mkdir -p $outpath && $xcassets --output-format human-readable-text --output-partial-info-plist $outplist"
                " --app-icon AppIcon --launch-image LaunchImage --platform macosx --minimum-deployment-target "
                + self.deploymenttarget
                + " --target-device mac --compress-pngs --compile $outpath $in >/dev/null"
            )
            self.xibcmd = (
                "$xib --target-device mac --module $module --minimum-deployment-target "
                + self.deploymenttarget
                + " --output-partial-info-plist $outplist --auto-activate-custom-fonts"
                " --output-format human-readable-text --compile $outpath $in"
            )
        elif self.target.is_ios():
            self.xcassetscmd = (
                "mkdir -p $outpath && $xcassets --output-format human-readable-text --output-partial-info-plist $outplist"
                " --app-icon AppIcon --launch-image LaunchImage --platform iphoneos --minimum-deployment-target "
                + self.deploymenttarget
                + " --target-device iphone --target-device ipad --compress-pngs --compile $outpath $in >/dev/null"
            )
            self.xibcmd = (
                "$xib --target-device iphone --target-device ipad --module $module --minimum-deployment-target "
                + self.deploymenttarget
                + " --output-partial-info-plist $outplist --auto-activate-custom-fonts"
                " --output-format human-readable-text --compile $outpath $in &> /dev/null "
            )
        self.dsymutilcmd = "$dsymutil $in -o $outpath"
        self.codesigncmd = "build/ninja/codesign.py --target $target --prefs codesign.json --builddir $builddir --binname $binname --config $config --entitlements $entitlements $outpath"

    def parse_default_variables(self, variables):
        if not variables:
            return
        if isinstance(variables, dict):
            iterator = iter(variables.items())
        else:
            iterator = iter(variables)
        for key, val in iterator:
            if key == "deploymenttarget":
                self.deploymenttarget = val
            if key == "organisation":
                self.organisation = val
            if key == "bundleidentifier":
                self.bundleidentifier = val
            if key == "provisioning":
                self.provisioning = val

    def parse_prefs(self, prefs):
        if self.target.is_ios() and "ios" in prefs:
            iosprefs = prefs["ios"]
            if "deploymenttarget" in iosprefs:
                self.deploymenttarget = iosprefs["deploymenttarget"]
            if "organisation" in iosprefs:
                self.organisation = iosprefs["organisation"]
            if "bundleidentifier" in iosprefs:
                self.bundleidentifier = iosprefs["bundleidentifier"]
            if "provisioning" in iosprefs:
                self.provisioning = iosprefs["provisioning"]
        elif self.target.is_macos() and "macos" in prefs:
            macosprefs = prefs["macos"]
            if "deploymenttarget" in macosprefs:
                self.deploymenttarget = macosprefs["deploymenttarget"]
            if "organisation" in macosprefs:
                self.organisation = macosprefs["organisation"]
            if "bundleidentifier" in macosprefs:
                self.bundleidentifier = macosprefs["bundleidentifier"]
            if "provisioning" in macosprefs:
                self.provisioning = macosprefs["provisioning"]

    def write_variables(self, writer):
        writer.variable("plist", self.plist)
        writer.variable("xcassets", self.xcassets)
        writer.variable("xib", self.xib)
        writer.variable("dsymutil", self.dsymutil)
        writer.variable("bundleidentifier", syntax.escape(self.bundleidentifier))
        writer.variable("deploymenttarget", self.deploymenttarget)
        writer.variable("entitlements", "none")

    def write_rules(self, writer):
        writer.rule(
            "dsymutil", command=self.dsymutilcmd, description="DSYMUTIL $outpath"
        )
        writer.rule("plist", command=self.plistcmd, description="PLIST $outpath")
        writer.rule(
            "xcassets", command=self.xcassetscmd, description="XCASSETS $outpath"
        )
        writer.rule("xib", command=self.xibcmd, description="XIB $outpath")
        writer.rule(
            "codesign", command=self.codesigncmd, description="CODESIGN $outpath"
        )

    def make_bundleidentifier(self, binname):
        return self.bundleidentifier.replace("$(binname)", binname)

    def app(
        self,
        toolchain,
        writer,
        module,
        archbins,
        outpath,
        binname,
        basepath,
        config,
        implicit_deps,
        resources,
        codesign,
    ):
        # Outputs
        builtbin = []
        builtres = []
        builtsym = []

        # Paths
        builddir = os.path.join("$buildpath", config, "app", binname)
        configpath = os.path.join(outpath, config)
        apppath = os.path.join(configpath, binname + ".app")
        dsympath = os.path.join(outpath, config, binname + ".dSYM")

        # Extract debug symbols from universal binary
        dsymcontentpath = os.path.join(dsympath, "Contents")
        builtsym = writer.build(
            [
                os.path.join(dsymcontentpath, "Resources", "DWARF", binname),
                os.path.join(dsymcontentpath, "Resources", "DWARF"),
                os.path.join(dsymcontentpath, "Resources"),
                os.path.join(dsymcontentpath, "Info.plist"),
                dsymcontentpath,
                dsympath,
            ],
            "dsymutil",
            archbins[config],
            variables=[("outpath", dsympath)],
        )

        # Copy final universal binary
        if self.target.is_ios():
            builtbin = toolchain.copy(
                writer,
                archbins[config],
                os.path.join(apppath, toolchain.binprefix + binname + toolchain.binext),
            )
        else:
            builtbin = toolchain.copy(
                writer,
                archbins[config],
                os.path.join(
                    apppath,
                    "Contents",
                    "MacOS",
                    toolchain.binprefix + binname + toolchain.binext,
                ),
            )

        # Build resources
        if resources:
            has_resources = False

            # Lists of input plists and partial plist files produced by resources
            plists = []
            assetsplists = []
            xibplists = []
            entitlements = []

            # All resource output files
            outfiles = []

            # First build everything except plist inputs
            for resource in resources:
                if resource.endswith(".xcassets"):
                    if self.target.is_macos():
                        assetsvars = [
                            (
                                "outpath",
                                os.path.join(
                                    os.getcwd(), apppath, "Contents", "Resources"
                                ),
                            )
                        ]
                    else:
                        assetsvars = [("outpath", apppath)]
                    outplist = os.path.join(
                        os.getcwd(),
                        builddir,
                        os.path.splitext(os.path.basename(resource))[0]
                        + "-xcassets.plist",
                    )
                    assetsvars += [("outplist", outplist)]
                    outfiles = [outplist]
                    if self.target.is_macos():
                        outfiles += [
                            os.path.join(
                                os.getcwd(),
                                apppath,
                                "Contents",
                                "Resources",
                                "AppIcon.icns",
                            )
                        ]
                    elif self.target.is_ios():
                        pass  # TODO: Need to list all icon and launch image files here
                    assetsplists += writer.build(
                        outfiles,
                        "xcassets",
                        os.path.join(os.getcwd(), basepath, module, resource),
                        variables=assetsvars,
                    )
                    has_resources = True
                elif resource.endswith(".xib"):
                    xibmodule = binname.replace("-", "_").replace(".", "_")
                    if self.target.is_macos():
                        nibpath = os.path.join(
                            apppath,
                            "Contents",
                            "Resources",
                            os.path.splitext(os.path.basename(resource))[0] + ".nib",
                        )
                    else:
                        nibpath = os.path.join(
                            apppath,
                            os.path.splitext(os.path.basename(resource))[0] + ".nib",
                        )
                    plistpath = os.path.join(
                        builddir,
                        os.path.splitext(os.path.basename(resource))[0] + "-xib.plist",
                    )
                    xibplists += [plistpath]
                    outfiles = []
                    if self.target.is_ios():
                        outfiles += [
                            os.path.join(nibpath, "objects.nib"),
                            os.path.join(nibpath, "objects-8.0+.nib"),
                            os.path.join(nibpath, "runtime.nib"),
                        ]
                    outfiles += [nibpath, plistpath]
                    builtres += writer.build(
                        outfiles,
                        "xib",
                        os.path.join(os.getcwd(), basepath, module, resource),
                        variables=[
                            ("outpath", nibpath),
                            ("outplist", plistpath),
                            ("module", xibmodule),
                        ],
                    )
                    has_resources = True
                elif resource.endswith(".plist"):
                    plists += [os.path.join(os.getcwd(), basepath, module, resource)]
                elif resource.endswith(".entitlements"):
                    entitlements += [
                        os.path.join(os.getcwd(), basepath, module, resource)
                    ]

            # Extra output files/directories
            outfiles = []
            if has_resources and self.target.is_macos():
                outfiles += [os.path.join(apppath, "Contents", "Resources")]

            # Now build input plists appending partial plists created by previous resources
            if self.target.is_macos():
                plistpath = os.path.join(apppath, "Contents", "Info.plist")
                pkginfopath = os.path.join(apppath, "Contents", "PkgInfo")
            else:
                plistpath = os.path.join(apppath, "Info.plist")
                pkginfopath = os.path.join(apppath, "PkgInfo")
            plistvars = [
                ("exename", binname),
                ("prodname", binname),
                ("outpath", plistpath),
            ]
            bundleidentifier = self.make_bundleidentifier(binname)
            if bundleidentifier != "":
                plistvars += [("bundleidentifier", bundleidentifier)]
            outfiles += [plistpath, pkginfopath]
            builtres += writer.build(
                outfiles,
                "plist",
                plists + assetsplists + xibplists,
                implicit=[os.path.join("build", "ninja", "plist.py")],
                variables=plistvars,
            )

        # Do code signing (might modify binary, but does not matter, nothing should have final binary as input anyway)
        if codesign:
            codesignvars = [
                ("builddir", builddir),
                ("binname", binname),
                ("outpath", apppath),
                ("config", config),
            ]
            if self.target.is_ios():
                if self.provisioning != "":
                    codesignvars += [("provisioning", self.provisioning)]
                writer.build(
                    [
                        os.path.join(apppath, "_CodeSignature", "CodeResources"),
                        os.path.join(apppath, "_CodeSignature"),
                        apppath,
                    ],
                    "codesign",
                    builtbin,
                    implicit=builtres + [os.path.join("build", "ninja", "codesign.py")],
                    variables=codesignvars,
                )
            elif self.target.is_macos():
                if self.provisioning != "":
                    codesignvars += [("provisioning", self.provisioning)]
                if len(entitlements) > 0:
                    codesignvars += [("entitlements", entitlements[0])]
                writer.build(
                    [
                        os.path.join(
                            apppath, "Contents", "_CodeSignature", "CodeResources"
                        ),
                        os.path.join(apppath, "Contents", "_CodeSignature"),
                        os.path.join(apppath, "Contents"),
                        apppath,
                    ],
                    "codesign",
                    builtbin,
                    implicit=builtres + [os.path.join("build", "ninja", "codesign.py")],
                    variables=codesignvars,
                )

        return builtbin + builtsym + builtres
